#!/bin/bash
#: Title    : rbfstab
#: Author   : "John Lehr" <slo.sleuth@gmail.com>
#: Date     : 10/31/2011
#: Version  : 0.1.2
#: Desc     : rewrite fstab on hotplug for write protection
#: Options  : -i install, -r remove

#: 08/23/11 : v0.1.0, first release
#: 09/09/11 : v0.1.1, implemented 'noload' option for ext3/4, no longer
#:          : forced ext2, disabled all swap
#: 09/14/11 : v0.1.2, bug fixes in rm_unused_dirs, already running check
#: 10/31/11 : v0.1.3, bug fix to keep mount points for plugged devices
#:          : after rbfstab -r
#: 12/15/11 : v0.1.2, bug fix for ext3: noload option not accepted by
#:          : mount even though suggested in manual, forced ext2 instead

## Variables
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
TMP="/tmp/fstab.$$.tmp"
ADDEDBY="# by rbfstab"
WRITE_LABEL="RBFSTAB"
SCRIPT=/usr/sbin/${0##*/}
RULE=/etc/udev/rules.d/fstab.rules

## Functions
install_script()
{
    echo "$0 first time run:"
    echo "* Copying $0 to $SCRIPT"
    cp $0 /usr/sbin/
    echo "* Creating udev rule $RULE"
    cat << EOF > $RULE
# Force fstab options for devices
RUN+="/usr/sbin/rbfstab"
EOF
    echo "* Restarting udev service"
    sudo service udev restart
    echo "Installation of $0 complete."
    rbfstab
    exit 0
}

remove_script()
{
    rm -vf $RULE 
    grep -v "$ADDEDBY" /etc/fstab > "$TMP"
    mv "$TMP" /etc/fstab
    echo "* Removed ${0##*/} rules from /etc/fstab"
    remove_unused_dirs
    exit 0
}

remove_unused_dirs()
{
    DEV=$(blkid -o device)
    for DIR in $(ls /media/)
    do
        [[ $DIR =~ cdrom ]] && continue
        echo "$DEV" | grep -q /dev/$DIR
        [ $? = 0 ] || rmdir /media/$DIR
    done
}

usage()
{
    cat << EOF
Usage: ${0##*/} [-ihr]

Options:
    -i install 
    -r remove
    -h this help

${0##*/} installs a script activated by a udev rule that writes
read-only options to fstab to assist with forensic examinations.
EOF
}

## Run as root warning
[ $UID -ne 0 ] && echo "You must be root." && exit 1 

## list of options program will accept;
## options followed by a colon take arguments
optstring=ihr

INSTALL=0
REMOVE=0

## The loop calls getops until there are no more options on the command 
## line.  Each option is stored in $opt, any option arguments are stored
## in OPTARG
while getopts $optstring opt; do
    case $opt in
        i) INSTALL=1 ;;
        h) USAGE >&2; exit 0 ;;
        r) REMOVE=1 ;;
        *) echo; usage >&2; exit 1 ;;
	esac
done

## Remove options from the command line
## $OPTIND points to the next, unparsed argument
shift "$(( $OPTIND -1 ))"

## Check for conflicting arguments
if [ $(($INSTALL+$REMOVE)) -gt 1 ]; then
	echo "Error: only one argument may be used at a time" >&2
    usage
	exit 1
fi

## Process options
[ $INSTALL = 1 ] && install_script
[ $REMOVE = 1 ] && remove_script

## disable swap (remove for installed OS if desired)
swapoff -a

## Exit if script is already running
if [ -e /var/run/rbfstab.pid ]; then
 ps "$(</var/run/rbfstab.pid)" >/dev/null 2>&1 && exit 0
 rm -f /var/run/rbfstab.pid
fi

echo "$$" > /var/run/rbfstab.pid

## remove rbfstab rules from existing /etc/fstab
grep -v "$ADDEDBY" /etc/fstab > $TMP

## Loop through attached block devices found in sysfs
for DEVICE in $(blkid -o device)
do
    ## skip loop, ram, and network boot devices
    case $DEVICE in
        *loop*|*ram*|*nbd*) continue ;;
    esac

    ## skip fstab entry for devices present during OS install
    grep -q $(blkid -s UUID -o value $DEVICE) /etc/fstab
    [ $? = 0 ] && continue
    
    ## identify file system type for device
    FSTYPE=$(blkid -s TYPE -o value "$DEVICE")

    ## skip fstab entry if no file system detected
    [ -z "$FSTYPE" ] && continue
    
    ## create a mountpoint for device if none exists
    MOUNTPOINT="${DEVICE//dev/media}"
    [ -d $MOUNTPOINT ] || mkdir -p $MOUNTPOINT 
    
    ## default read-only options
    OPTIONS="ro,noauto,noexec,nodev,noatime"

    ## file system specific options
    case "$FSTYPE" in
        ntfs) FSTYPE="ntfs-3g"; OPTIONS="${OPTIONS},umask=000,\
show_sys_files,streams_interface=windows,allow_other" ;;
        msdos) OPTIONS="${OPTIONS},umask=000,quiet" ;;
        vfat) OPTIONS="${OPTIONS},umask=000,shortname=mixed,quiet" ;;
        ext) OPTIONS="${OPTIONS}" ;;
        ext2) OPTIONS="${OPTIONS}" ;;
        ext3) OPTIONS="${OPTIONS}" ;;
        ext4) OPTIONS="${OPTIONS},noload" ;;
        xfs) OPTIONS="norecovery,${OPTIONS},loop" ;;
        jfs) OPTIONS="nointegrity,${OPTIONS}" ;;
        reiserfs) OPTIONS="nolog,${OPTIONS},loop" ;;
        hfs) FSTYPE="hfsplus"; OPTIONS="${OPTIONS}" ;;
        hfsplus) OPTIONS="${OPTIONS}" ;;
        swap) OPTIONS=ro,noauto ;;
    esac

	[ "$FSTYPE" = "ext3" ] && FSTYPE="ext2"
	
    ## Make partition with label $WRITE_LABEL writeable
    if [ "$(blkid -s LABEL -o value "$DEVICE")" = "LABEL=\"$WRITE_LABEL\"" ]
    then
        echo "\"$WRITE_LABEL\" save partition found at $DEVICE, write enabled."
        OPTIONS="default"
    fi

    # Add modified mounting options to fstab
    printf "%-15s %-15s %-8s %-20s %-s\n" "$DEVICE" "$MOUNTPOINT" "$FSTYPE" "$OPTIONS" "0 0 $ADDEDBY" >> $TMP
done


## write new /ect/fstab
mv $TMP /etc/fstab

## remove process file for next run, remove unused dirs in /media
rm -f /var/run/rbfstab.pid
remove_unused_dirs

## Flush file system buffers
sync

exit 0
